from django_redis import get_redis_connection
from rest_framework.response import Response
from django.core.paginator import Paginator
from rest_framework.views import APIView
from django.db.models import F
# from django.db.models import Sum
from django.db import transaction
from shop import models
import datetime
import requests
import urllib3
import django
import redis
import json


def shop_rest(shop: str) -> (int, int):
    """分店库存查询函数
    :param shop: 查询的分店名
    """

    try:
        # 获取请求查询分店实例
        res = models.Shops.objects.get(name=shop)
    except models.Shops.DoesNotExist:
        # 向上抛出异常
        raise models.Shops.DoesNotExist

    # 返回 鸡、鸭 库存
    return int(res.chicken), int(res.duck)


def authentication(openid: str, shop: None) -> (models.Clerks, str):
    """用户身份验证"""

    # 获取请求数据的用户实例
    user = models.Clerks.objects.filter(openid=openid).first()

    # 店员表中没有该用户
    if not user:
        return None, '您是无效用户！请注册'

    # 如果用户为“仅注册”状态
    if user.identity == 0:
        return None, '老板还未确认身份！请等待'

    # 如果参数 shop 不为 None
    if shop:
        # 如果店员所属分店与请求不符 且 其不是老板
        if shop != user.shop.name and user.identity != 2:
            return None, '您无相关权限！'

    return user, ''


def meta_info(msg: str, status: int) -> dict:
    """状态信息函数
    :param msg: 状态信息内容
    :param status: 状态代码
    """

    return {'msg': msg, 'status': status}


def redis_info(shop: str, date: tuple, con_type: str) -> (redis.client.Redis, str):
    """redis 数据库连接信息函数
    :param shop: 查询分店在数据库中的 id
    :param date: 查询的时间
    :param con_type: 连接的类型
    :return: redis 的连接对象, 查询的表名
    """

    return get_redis_connection('default'), shop + '_' + '-'.join(date) + '_' + con_type


def paging(content, num: int, request_page: str) -> django.core.paginator.Page:
    """对查询结果分页"""
    
    paginator = Paginator(content, num)
    try:
        page = int(request_page)
    except Exception:
        raise ValueError('页数出错！请重试')

    # 请求页数超过总页数
    if page > paginator.num_pages:
        raise ValueError('页数溢出！')

    return paginator.page(page)


class WXminiprogramInfo:
    """微信小程序相关信息"""

    # 小程序 ID
    AppID = 'wx761e7177bb460936'

    # 小程序密钥
    APPSecret = 'fdb43fc901b099250db5d1789fff50c8'

    # access_token 请求地址
    getAccessToken = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={}&secret={}'

    # 发送订阅消息请求地址
    send = 'https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token={}'

    # code 请求地址
    code2Session = 'https://api.weixin.qq.com/sns/jscode2session?appid={}&secret={}&js_code={}&grant_type=authorization_code'

    template_ids = {
        # 调货请求订阅消息模板
        'sendTransfer': 'bg_UdlGmpiBwB66WU2mTGZm8TW-kHjhIdJSkcFkmcks',
        # 处理调货订阅消息模板
        'handleTransfer': 'abj1o9OVeho6lIZWkVX_fLsLB83__Sou5mODVkK9wzk'
    }

    # 支出成本类型
    cost_types = ['电费', '水费', '店面费', '员工费', '运输费']

    @classmethod
    def get_token(cls) -> (str, int):
        """获取 token"""

        # 拼接请求地址
        request_url = cls.getAccessToken.format(cls.AppID, cls.APPSecret)

        # 构造参数字典
        param = {
            'grant_type': 'client_credential',
            'appid': cls.AppID,
            'secret': cls.APPSecret
        }

        # 发起 get 请求
        response = requests.get(request_url, param)

        # 把返回结果转换成 json 字典
        response_json = response.json()

        return response_json


class WXLoginAPIView(APIView):
    """微信小程序用户登录"""

    def post(self, request):
        param = request.data

        # 如果没有参数 code
        if not param.get('code'):
            return Response({
                'meta': meta_info('登录缺少必要参数！请重试', 1)
            })

        # 拼接请求地址
        code_url = WXminiprogramInfo.code2Session.format(
            WXminiprogramInfo.AppID, WXminiprogramInfo.APPSecret, param['code']
        )

        # 构造参数字典
        param = {
            'appid': WXminiprogramInfo.AppID,
            'secret': WXminiprogramInfo.APPSecret,
            'js_code': param['code'],
            'grant_type': 'authorization_code',
        }

        # 取消不安全警告
        urllib3.disable_warnings()
        # 发起 get 请求
        response = requests.get(code_url, param, verify=False)

        # 把返回结果转换成 json 字典
        json_response = response.json()

        if json_response.get('openid'):
            return Response({
                'meta': meta_info('获取成功！', 0),
                'message': {
                    'openid': json_response['openid']
                }
            })
        else:
            return Response({
                'meta': meta_info('无效的code！', 2)
            })


class RegisterAPIView(APIView):
    """用户注册"""

    def post(self, request):
        param = request.data
        name = param['name']
        openid = param['openid']
        request_shop = models.Shops.objects.filter(name=param['shop']).first()

        # 没有所请求的店铺实例
        if not request_shop:
            return Response({
                'meta': meta_info('请求店铺不存在！', 1)
            })

        try:
            models.Clerks.objects.create(
                WXname=name,
                openid=openid,
                shop=request_shop
            )
        except Exception as e:
            print(repr(e))

            return Response({
                'meta': meta_info('注册失败！请重试', 2)
            })

        return Response({
            'meta': meta_info('注册成功！', 0)
        })


class LoginAPIView(APIView):
    """登录"""

    def post(self, request):
        try:
            user = models.Clerks.objects.filter(openid=request.data['openid']).first()
        except Exception as e:
            print(repr(e))

            return Response({
                'meta': meta_info('登录失败！请重试', 1)
            })

        # 数据库中没有该用户
        if not user:
            return Response({
                'meta': meta_info('用户不存在！', 2)
            })

        # 用户的共性信息
        message = {
            'identity': user.identity,
            'shop': user.shop.name
        }

        # 登录的用户身份不是“仅注册”
        if user.identity:
            # 获取用户店铺的鸡鸭库存
            chicken, duck = shop_rest(user.shop.name)
            message['chicken'] = chicken
            message['duck'] = duck

        return Response({
            'message': message,
            'meta': meta_info('登录成功！', 0)
        })


class Shop(APIView):
    """获取此时所有分店名称"""

    def get(self, request):
        try:
            names = []
            default_chickens = []
            default_ducks = []

            for item in models.Shops.objects.all():
                names.append(item.name)
                default_chickens.append(item.default_chicken)
                default_ducks.append(item.default_duck)

        except Exception as e:
            print(repr(e))

            return Response({
                'meta': meta_info('获取分店名称失败！请重试', 1)
            })

        return Response({
            'message': {
                'names': names,
                'default_chickens': default_chickens,
                'default_ducks': default_ducks
            },
            'meta': meta_info('获取成功！', 0)
        })


class ShopManage(APIView):
    """管理分店"""

    @transaction.atomic
    def post(self, request):
        param = request.data
        # 身份验证
        user = models.Clerks.objects.filter(openid=param['user']).first()

        if not user:
            return Response({
                'meta': meta_info('您为无效用户！请注册', 1)
            })
        elif user.identity != 2:
            return Response({
                'meta': meta_info('只有老板才能管理分店！', 2)
            })

        save_id = transaction.savepoint()

        # 添加新分店
        if param['operation'] == 'add':
            try:
                models.Shops.objects.create(
                    name=param['shop']
                )
            except Exception as e:
                print(repr(e))

                transaction.savepoint_rollback(save_id)
                return Response({
                    'meta': meta_info('添加失败', 3)
                })
            else:
                transaction.savepoint_commit(save_id)
                return Response({
                    'meta': meta_info('添加成功', 0)
                })
        # 修改 或 删除 分店
        else:
            try:
                shop = models.Shops.objects.get(name=param['shop'])
            except Exception as e:
                print(repr(e))

                transaction.savepoint_rollback(save_id)
                return Response({
                    'meta': meta_info('获取分店失败', 4)
                })
            else:
                # 删除分店
                if param['operation'] == 'delete':
                    try:
                        shop.delete()
                    except Exception as e:
                        print(repr(e))

                        transaction.savepoint_rollback(save_id)
                        return Response({
                            'meta': meta_info('删除失败', 5)
                        })
                    else:
                        transaction.savepoint_commit(save_id)
                        return Response({
                            'meta': meta_info('删除成功', 0)
                        })
                # 修改分店
                else:
                    try:
                        shop.default_chicken = param['chicken']
                        shop.default_duck = param['duck']
                        shop.save()
                    except Exception as e:
                        print(repr(e))

                        transaction.savepoint_rollback(save_id)
                        return Response({
                            'meta': meta_info('修改失败', 6)
                        })
                    else:
                        transaction.savepoint_commit(save_id)
                        return Response({
                            'meta': meta_info('修改成功', 0)
                        })


class AddPurchaseAPIView(APIView):
    """进货记录添加"""

    # 事务装饰器
    @transaction.atomic
    def post(self, request):
        param = request.data
        user = models.Clerks.objects.filter(openid=param['user']).first()

        if not user:
            return Response({
                'meta': meta_info('您是无效用户！请注册', 1)
            })

        if user.identity < 2:
            return Response({
                'meta': meta_info('权限不足！', 2)
            })

        # 所选分店为“库房”
        if param['shop'] == '库房':
            return Response({
                'meta': meta_info('无法为库房进货！', 3)
            })
        else:
            try:
                request_shop = models.Shops.objects.get(name=param['shop'])
            except Exception as e:
                print(repr(e))

                return Response({
                    'meta': meta_info('分店信息错误！请重试', 4)
                })

        # 参数传过来的时候是字符串类型，将其转换成 date 类型
        date = datetime.datetime.date(datetime.datetime.strptime(param['date'], "%Y-%m-%d"))
        chicken = int(param['chicken'])
        duck = int(param['duck'])

        # 获取仓库实例
        storage_room = models.Shops.objects.get(name='库房')

        if storage_room.chicken < chicken or storage_room.duck < duck:
            return Response({
                'meta': meta_info('库房库存不足！无法进货', 5)
            })

        # 设置保存点
        save_id = transaction.savepoint()
        try:
            # 向分销表中添加一条记录
            models.Distribution.objects.create(
                shop=request_shop,
                date=date,
                chicken=chicken,
                duck=duck,
            )

            # 更新进货店铺的 鸡、鸭 库存
            request_shop.chicken = F('chicken') + chicken
            request_shop.duck = F('duck') + duck
            request_shop.save()

            # 更新库房库存
            storage_room.chicken = F('chicken') - chicken
            storage_room.duck = F('duck') - duck
            storage_room.save()
        except Exception as e:
            print(repr(e))

            # 创建记录失败，回滚到保存点处
            transaction.savepoint_rollback(save_id)
            return Response({
                'meta': meta_info('添加失败！请重试', 6)
            })
        # 保存点业务提交
        transaction.savepoint_commit(save_id)

        return Response({
            'meta': meta_info('添加成功！', 0)
        })


class AddSaleAPIView(APIView):
    """分店销售添加"""

    @transaction.atomic
    def post(self, request):
        param = request.data
        shop = param['shop']
        user = models.Clerks.objects.filter(openid=param['user']).first()

        if not user:
            return Response({
                'meta': meta_info('您是无效用户！请注册', 1)
            })

        if user.identity == 0:
            return Response({
                'meta': meta_info('老板还未确认身份！请等待', 2)
            })

        # 用户的身份为“库管”
        if user.identity == 3:
            return Response({
                'meta': meta_info('库房无法添加销售', 3)
            })

        date = param['date']
        chicken_count = int(param['chicken'])
        chicken_unit = float(param['chicken_unit'])
        duck_count = int(param['duck'])
        duck_unit = float(param['duck_unit'])

        # 如果发起请求的用户为老板，则使用请求参数中的分店信息
        if user.identity == 2:
            request_shop = models.Shops.objects.filter(name=shop).first()
        # 如果发起请求的用户为店员，则直接使用其所属分店的信息即可
        else:
            request_shop = user.shop

        if not request_shop:
            return Response({
                'meta': meta_info('获取分店失败！请重试', 4)
            })

        # 销售数量大于分店库存量
        if request_shop.chicken < chicken_count or request_shop.duck < duck_count:
            return Response({
                'meta': meta_info('店铺库存小于销售数量！', 5)
            })

        # 查询日期信息列表
        date_info = date.split('-')

        # 创建 redis 对象，拼接查找字符串
        conn, research_key = redis_info(request_shop.name, (date, ), 'sale')

        try:
            # 如果 redis 中存在该缓存信息
            if conn.exists(research_key):
                # 删除 redis 中的相关缓存信息
                conn.delete(research_key)
            # 如果 redis 中存在所在月份的缓存信息
            elif conn.exists(shop + '_' + date_info[0] + '-' + date_info[1] + '_' + 'sale'):
                conn.delete(shop + '_' + date_info[0] + '-' + date_info[1] + '_' + 'sale')
        except Exception as e:
            print(repr(e))

            return Response({
                'meta': meta_info('添加失败！请重试', 6)
            })

        # 计算分店销售总价
        total_money = chicken_count * chicken_unit + duck_count * duck_unit

        # 设置保存点
        save_id = transaction.savepoint()
        try:
            # 向销售表中添加一条记录
            models.Sale.objects.create(
                shop=request_shop,
                date=date,
                chicken_count=chicken_count,
                chicken_unit_price=chicken_unit,
                duck_count=duck_count,
                duck_unit_price=duck_unit,
                money=total_money
            )
            # 更新销售店铺的库存记录
            request_shop.chicken = F('chicken') - chicken_count
            request_shop.duck = F('duck') - duck_count
            request_shop.save()
        except Exception as e:
            print(repr(e))

            # 创建记录失败，回滚到保存点处
            transaction.savepoint_rollback(save_id)
            return Response({
                'meta': meta_info('添加失败！请重试', 6)
            })
        # 保存点业务提交
        transaction.savepoint_commit(save_id)

        return Response({
            'meta': meta_info('添加成功！', 0)
        })


class CostType(APIView):
    """获取支出类型"""

    def get(self, request):
        try:
            types = []
            for name in WXminiprogramInfo.cost_types:
                types.append(name)
        except Exception as e:
            print(repr(e))

            return Response({
                'meta': meta_info('获取支出类型失败！请重试', 1)
            })
        return Response({
            'message': {
                'types': types
            },
            'meta': meta_info('获取成功！', 0)
        })


class AddCostAPIView(APIView):
    """分店成本支出添加"""

    @transaction.atomic
    def post(self, request):
        param = request.data
        shop = param['shop']
        user = models.Clerks.objects.filter(openid=param['user']).first()

        if not user:
            return Response({
                'meta': meta_info('您是无效用户！请注册', 1)
            })

        if user.identity == 0:
            return Response({
                'meta': meta_info('老板还未确认身份！请等待', 2)
            })

        # 用户的身份为“库管”
        if user.identity == 3:
            return Response({
                'meta': meta_info('库房无法添加支出', 3)
            })

        date = param['date']
        cost_type = param['type']
        money = float(param['money'])

        # 如果发起请求的用户为老板，则使用请求参数中的分店信息
        if user.identity == 2:
            request_shop = models.Shops.objects.get(name=shop)
        # 如果发起请求的用户为店员或店长，则直接使用其所属分店的信息即可
        else:
            request_shop = user.shop

        if not request_shop:
            return Response({
                'meta': meta_info('获取分店失败！请重试', 4)
            })

        # 查询日期信息列表
        date_info = date.split('-')

        # 创建 redis 对象，拼接查找字符串
        conn, research_key = redis_info(request_shop.name, (date_info[0], date_info[1]), 'cost')

        # 如果 redis 中存在该缓存结果
        if conn.exists(research_key):
            try:
                # 删除 redis 中的相关缓存结果
                conn.delete(research_key)
            except Exception as e:
                print(repr(e))

                return Response({
                    'meta': meta_info('添加失败！请重试', 5)
                })

        # 设置保存点
        save_id = transaction.savepoint()
        try:
            models.Cost.objects.create(
                shop=request_shop,
                cost_type=cost_type,
                money=money,
                date=date
            )
        except Exception as e:
            print(repr(e))

            # 记录添加失败，回滚到保存点处
            transaction.savepoint_rollback(save_id)
            return Response({
                'meta': meta_info('添加失败！请重试', 4)
            })
        # 保存点业务提交
        transaction.savepoint_commit(save_id)

        return Response({
            'meta': meta_info('添加成功！', 0)
        })


class StorageRoomPurchaseAPIView(APIView):
    """库房入库"""

    @transaction.atomic
    def post(self, request):
        param = request.data
        user = models.Clerks.objects.filter(openid=param['user']).first()
        # 用户不是 老板 或 库管
        if user.identity < 2:
            return Response({
                'meta': meta_info('身份权限过低！', 1)
            })

        supplier = param['supplier']
        date = datetime.datetime.date(datetime.datetime.strptime(param['date'], "%Y-%m-%d"))
        chicken_count = int(param['chicken'])
        # chicken_unit = float(param['chicken_unit'])
        duck_count = int(param['duck'])
        # duck_unit = float(param['duck_unit'])

        # 计算库房进货总金额
        # total_money = chicken_count * chicken_unit + duck_count * duck_unit

        # 设置保存点
        save_id = transaction.savepoint()
        try:
            # 向进货表中添加一条记录
            models.Purchase.objects.create(
                supplier=supplier,
                date=date,
                chicken_count=chicken_count,
                # chicken_unit_price=chicken_unit,
                duck_count=duck_count,
                # duck_unit_price=duck_unit,
                # money=total_money
            )

            # 获取库房实例
            storage_room = models.Shops.objects.get(name='库房')

            # 更新库房 鸡、鸭 库存
            storage_room.chicken = F('chicken') + chicken_count
            storage_room.duck = F('duck') + duck_count
            storage_room.save()
        except Exception as e:
            print(repr(e))

            # 添加记录失败，回滚到保存点处
            transaction.savepoint_rollback(save_id)
            return Response({
                'meta': meta_info('添加失败！请重试', 2)
            })
        # 保存点业务提交
        transaction.savepoint_commit(save_id)

        return Response({
            'meta': meta_info('添加成功！', 0)
        })


class ShopChangeAPIView(APIView):
    """改变分店，获取库存"""

    def post(self, request):
        try:
            chicken, duck = shop_rest(request.data['shop'])
        except Exception as e:
            print(repr(e))

            return Response({
                'meta': meta_info('库存量获取失败！请重试', 1)
            })
        return Response({
            'message': {
                'chicken': chicken,
                'duck': duck
            },
            'meta': meta_info('查询成功！', 0)
        })


class SearchSaleAPIView(APIView):
    """销售查询"""

    def post(self, request):
        param = request.data
        # 身份验证
        user, tip = authentication(param['user'], param['shop'])
        # 身份验证失败
        if not user:
            return Response({
                'meta': meta_info(tip, 1)
            })

        date = param['date']
        # 初始化返回列表
        message = []

        request_shop = models.Shops.objects.filter(name=param['shop']).first()
        if not request_shop:
            return Response({
                'meta': meta_info('获取分店失败！', 2)
            })

        # 查询日期信息列表
        date_info = date.split('-')

        conn, research_key = redis_info(request_shop.name, (date, ), 'sale')

        # 按月份查询
        if len(date_info) == 2:
            # redis 中不存在该缓存结果
            if not conn.exists(research_key):
                # 先查询出符合条件的所有分店的销售情况
                res = models.Sale.objects.filter(date__year=int(date_info[0]),
                                                 date__month=int(date_info[1])).order_by("-date", "-id")
            # redis 中存在该缓存结果
            else:
                # 获取 redis 中对应 key 的 zset
                res = conn.zrange(research_key, 0, -1, desc=True, withscores=True)

                # 重新设置 1 小时过期时间
                conn.expire(research_key, 3600)

                # 按每页 10 条数据进行分页
                try:
                    sales_page = paging(res, 10, param['page'])
                except Exception as e:
                    return Response({
                        'meta': meta_info(str(e), 3)
                    })

                # item 包含记录的信息，day 表示记录中的天数
                for item, day in sales_page:
                    # 将 字节变量 转换为 str 变量
                    item = item.decode()
                    # 将记录信息从字符串转换为列表
                    temp_item = item.split('_')

                    # 初始化中间字典
                    temp = dict()
                    temp['shop'] = temp_item[1]
                    temp['date'] = day
                    temp['chicken_count'] = int(temp_item[2])
                    temp['duck_count'] = int(temp_item[3])
                    temp['chicken_unit_price'] = float(temp_item[4])
                    temp['duck_unit_price'] = float(temp_item[5])
                    temp['money'] = float(temp_item[6])
                    message.append(temp)

                return Response({
                    'message': {
                        'res': message,
                        'total': conn.zcard(research_key)
                    },
                    'meta': meta_info('查询成功！', 0)
                })
        # 按日期查询
        else:
            # redis 中不存在该缓存结果，也不存在日期所在月份的对应结果
            if not conn.exists(research_key) and not conn.exists(
                    request_shop.name + '_' + date_info[0] + '-' + date_info[1] + '_' + 'sale'
            ):

                # 先查询出符合条件的所有分店的销售情况
                res = models.Sale.objects.filter(
                    date=datetime.datetime.date(datetime.datetime.strptime(date, "%Y-%m-%d"))
                ).order_by('-id')
            # redis 中存在该缓存结果
            else:
                # 存在天数相同的缓存记录
                if conn.exists(research_key):
                    # 重新设置过期时间
                    conn.expire(research_key, 3600)
                    # 获取 redis 中对应 key 的 zset
                    res = conn.zrange(research_key, 0, -1, desc=True, withscores=True)
                # 存在所在月份的缓存记录
                else:
                    # 重新设置过期时间
                    conn.expire(request_shop.name + '_' + date_info[0] + '-' + date_info[1] + '_' + 'sale', 3600)
                    # 找出对应 key 的 zset 中相关天数的缓存记录
                    res = conn.zrevrangebyscore(
                        request_shop.name + '_' + date_info[0] + '-' + date_info[1] + '_' + 'sale',
                        date_info[2],
                        date_info[2],
                        withscores=True
                    )

                # 按每页 10 条数据进行分页
                try:
                    sales_page = paging(res, 10, param['page'])
                except Exception as e:
                    return Response({
                        'meta': meta_info(str(e), 3)
                    })

                # 构造结果列表
                for item, day in sales_page:
                    # 将 字节变量 转换为 str变量
                    item = item.decode()
                    # 将记录信息从字符串转换为列表
                    temp_item = item.split('_')
                    # 初始化中间字典
                    temp = dict()
                    temp['shop'] = temp_item[1]
                    temp['date'] = day
                    temp['chicken_count'] = int(temp_item[2])
                    temp['duck_count'] = int(temp_item[3])
                    temp['chicken_unit_price'] = float(temp_item[4])
                    temp['duck_unit_price'] = float(temp_item[5])
                    temp['money'] = float(temp_item[6])
                    message.append(temp)

                return Response({
                    'message': {
                        'res': message,
                        'total': len(res)
                    },
                    'meta': meta_info('查询成功！', 0)
                })

        # 请求查询的是分店，在所有销售情况中进行筛选
        if param['shop'] != '库房':
            res = res.filter(shop__name=param['shop'])

        # 把查询结果保存到 redis 中
        for item in res:
            temp = (
                str(item.id),
                str(item.shop.name),
                str(item.chicken_count),
                str(item.duck_count),
                str(item.chicken_unit_price),
                str(item.duck_unit_price),
                str(item.money)
            )
            # element 为记录信息，score 为日期
            conn.zadd(research_key, {'_'.join(temp): int(item.date.day)})

        # 设置 1小时 的过期时间
        conn.expire(research_key, 3600)

        # 按每页 10 条数据进行分页
        try:
            sales_page = paging(res, 10, param['page'])
        except Exception as e:
            return Response({
                'meta': meta_info(str(e), 3)
            })

        # 把查询结果构造成字典
        for item in sales_page:
            # 初始化中间字典
            temp = dict()
            temp['shop'] = item.shop.name
            temp['date'] = item.date.day
            temp['chicken_count'] = item.chicken_count
            temp['duck_count'] = item.duck_count
            temp['chicken_unit_price'] = item.chicken_unit_price
            temp['duck_unit_price'] = item.duck_unit_price
            temp['money'] = item.money
            message.append(temp)

        return Response({
            'message': {
                'res': message,
                'total': len(res)
            },
            'meta': meta_info('查询成功！', 0)
        })


# class SearchProfitAPIView(APIView):
#     """毛利润查询"""
#
#     def post(self, request):
#         param = request.data
#         shop = int(param['shop'])
#         # 身份验证
#         user, tip = authentication(param['user'], shop)
#         # 身份验证失败
#         if not user:
#             return Response({
#                 'meta': meta_info(tip, 1)
#             })
#
#         date = param['date']
#
#         # 查询日期信息列表
#         date_info = date.split('-')
#
#         # 创建 redis 对象，拼接查找字符串
#         conn, research_key = redis_info(shop.name, (date, ), 'profit')
#         try:
#             # redis 中不存在该缓存结果
#             if not conn.exists(research_key):
#                 # 从 mysql 中查找结果
#                 # 总体总销售收入
#                 sale = models.Sale.objects.filter(date__year=int(date_info[0])).filter(date__month=int(date_info[1]))
#                 # 总体微信支付销售收入
#                 sale_wx = sale.filter(type=0)
#                 # 总体现金支付销售收入
#                 sale_cash = sale.filter(type=1)
#                 # 总体成本支出
#                 cost = models.Cost.objects.filter(date__year=int(date_info[0])).filter(date__month=int(date_info[1]))
#
#                 # 请求查询的是分店，则继续按照条件查询
#                 if shop != 4:
#                     # 分店总销售收入
#                     sale = sale.filter(shop__name=shop)
#                     # 分店微信支付销售收入
#                     sale_wx = sale_wx.filter(shop__name=shop)
#                     # 分店现金支付销售收入
#                     sale_cash = sale_cash.filter(shop__name=shop)
#                     # 分店成本支出
#                     cost = cost.filter(shop__name=shop)
#
#                 # 计算查询结果的总和
#                 sale = sale.aggregate(value=Sum('money'))
#                 if not sale['value']:
#                     sale['value'] = 0
#
#                 sale_wx = sale_wx.aggregate(value=Sum('money'))
#                 if not sale_wx['value']:
#                     sale_wx['value'] = 0
#
#                 sale_cash = sale_cash.aggregate(value=Sum('money'))
#                 if not sale_cash['value']:
#                     sale_cash['value'] = 0
#
#                 cost = cost.aggregate(value=Sum('money'))
#                 if not cost['value']:
#                     cost['value'] = 0
#
#                 # 把查询结果加入 redis 中
#                 conn.lpush(
#                     research_key,
#                     str(sale['value']),
#                     str(sale_wx['value']),
#                     str(sale_cash['value']),
#                     str(cost['value'])
#                 )
#
#             # 设置 1小时 过期时间
#             conn.expire(research_key, 3600)
#
#             # 获取 redis 中缓存的相关信息
#             sale = conn.lindex(research_key, 3).decode()
#             sale_wx = conn.lindex(research_key, 2).decode()
#             sale_cash = conn.lindex(research_key, 1).decode()
#             cost = conn.lindex(research_key, 0).decode()
#         except:
#             return Response({
#                 'meta': meta_info('查询失败！请重试', 2)
#             })
#
#         return Response({
#             'message': {
#                 'sale': float(sale),
#                 'sale_wx': float(sale_wx),
#                 'sale_cash': float(sale_cash),
#                 'cost': float(cost)
#             },
#             'meta': meta_info('查询成功！', 0)
#         })


class SearchCostAPIView(APIView):
    """成本支出查询"""

    def post(self, request):
        param = request.data
        user = models.Clerks.objects.filter(openid=param['user']).first()

        if not user:
            return Response({
                'meta': meta_info('您是无效用户！请注册', 1)
            })

        if not user.identity:
            return Response({
                'meta': meta_info('老板还未确认身份！请等待', 2)
            })

        # 用户的身份为“库管”
        if user.identity == 3:
            return Response({
                'meta': meta_info('库房没有相关信息', 3)
            })

        date = param['date']

        # 初始化返回列表
        cost_message = []

        request_shop = models.Shops.objects.filter(name=param['shop']).first()
        if not request_shop:
            return Response({
                'meta': meta_info('分店获取失败！', 4)
            })

        # 查询日期信息列表
        date_info = date.split('-')

        # 创建 redis 对象，拼接查找字符串
        conn, research_key = redis_info(request_shop.name, (date, ), 'cost')
        try:
            # redis 中不存在该缓存结果
            if not conn.exists(research_key):
                # 从 mysql 中查询结果
                costs = models.Cost.objects.filter(
                    shop__name=param['shop'],
                    date__year=int(date_info[0])
                ).filter(
                    date__month=int(date_info[1])
                ).order_by('date')

                for cost in costs:
                    # 构造记录信息元组
                    temp = (
                        str(cost.date.day),
                        cost.cost_type,
                        str(cost.money)
                    )
                    # 把记录插入 redis 中对应的 list 中
                    conn.rpush(research_key, '_'.join(temp))

            # 从 redis 中获取缓存结果
            costs = conn.lrange(research_key, 0, -1)

            # 重新设置过期时间
            conn.expire(research_key, 3600)

            # 按每页 10 条数据进行分页
            try:
                costs_page = paging(costs, 10, param['page'])
            except Exception as e:
                return Response({
                    'meta': meta_info(str(e), 5)
                })

            for item in costs_page:
                temp_list = item.decode().split('_')
                temp = dict()
                temp['shop'] = param['shop']
                temp['date'] = temp_list[0]
                temp['type'] = temp_list[1]
                temp['money'] = float(temp_list[2])
                cost_message.append(temp)
        except Exception as e:
            print(repr(e))

            return Response({
                'meta': meta_info('查询失败！请重试', 5)
            })

        return Response({
            'message': {
                'cost_res': cost_message,
                'total': conn.llen(research_key)
            },
            'meta': meta_info('查询成功！', 0)
        })


class SearchClerkAPIView(APIView):
    """用户管理查询"""

    def post(self, request):
        param = request.data
        user = models.Clerks.objects.filter(openid=param['user']).first()
        # 不存在该用户 或 该用户不是老板
        if not user or user.identity != 2:
            return Response({
                'meta': meta_info('只用老板才能权限！', 1)
            })

        try:
            user_list = models.Clerks.objects.filter(identity=param['identity'],
                                                     shop__name=param['shop']).order_by('WXname')
        except Exception as e:
            print(repr(e))

            return Response({
                'meta': meta_info('内容获取失败！请重试', 2)
            })

        # 按每页 10 条数据进行分页
        try:
            users_page = paging(user_list, 10, param['page'])
        except Exception as e:
            return Response({
                'meta': meta_info(str(e), 3)
            })

        # 初始化结果列表
        res_list = []

        for user in users_page:
            # 初始化中间字典
            temp = dict()
            temp['name'] = user.WXname
            temp['openid'] = user.openid
            res_list.append(temp)

        return Response({
            'message': {
                'res': res_list,
                'total': len(user_list)
            },
            'meta': meta_info('查询成功！', 0)
        })


class OperateUserAPIView(APIView):
    """处理用户请求"""

    @transaction.atomic
    def post(self, request):
        param = request.data
        user = models.Clerks.objects.filter(openid=param['user']).first()
        # 不存在该用户 或 该用户不是老板
        if not user or user.identity != 2:
            return Response({
                'meta': meta_info('只有老板才有此权限！', 1)
            })

        request_openid = param['request_openid']
        identity = param['identity']
        # 获取用户实例
        request_user = models.Clerks.objects.filter(openid=request_openid, identity=identity).first()
        if not request_user:
            return Response({
                'meta': meta_info('处理用户出错！请重试', 2)
            })

        # 设置保存点
        save_id = transaction.savepoint()
        try:
            # 处理“仅注册”用户
            if not identity:
                # 确认请求
                if param['operation'] == 'register':
                    # 用户申请库管
                    if request_user.shop.name == '库房':
                        request_user.identity = 3
                    # 用户申请分店
                    else:
                        request_user.identity = 1
                    request_user.save()
                # 拒绝请求
                else:
                    request_user.delete()
            else:
                request_user.delete()
        except Exception as e:
            print(repr(e))

            # 身份修改失败，回滚到保存点处
            transaction.savepoint_rollback(save_id)
            return Response({
                'meta': meta_info('处理失败！请重试', 3)
            })
        # 保存点业务提交
        transaction.savepoint_commit(save_id)

        return Response({
            'meta': meta_info('处理成功！', 0)
        })


class ModifyStorageRoom(APIView):
    """修改库房库存"""

    @transaction.atomic
    def post(self, request):
        param = request.data
        user = models.Clerks.objects.filter(openid=param['user']).first()

        # 不存在该用户 或 该用户不是店长
        if not user or user.identity != 2:
            return Response({
                'meta': meta_info('只有老板才有此权限！', 1)
            })

        storage_room = models.Shops.objects.filter(name='库房').first()
        if not storage_room:
            return Response({
                'meta': meta_info('获取库房信息失败！', 2)
            })

        # 设置保存点
        save_id = transaction.savepoint()
        try:
            storage_room.chicken = param['chicken']
            storage_room.duck = param['duck']
            storage_room.save()
        except Exception as e:
            print(repr(e))

            # 库存修改失败，回滚到保存点
            transaction.savepoint_rollback(save_id)
            return Response({
                'meta': meta_info('库存修改失败！请重试', 3)
            })
        # 保存点业务提交
        transaction.savepoint_commit(save_id)

        return Response({
            'meta': meta_info('修改成功！', 0)
        })


class GetOwnerOpenId(APIView):
    """获取分店店员 openid"""

    def post(self, request):
        param = request.data
        user = models.Clerks.objects.filter(openid=param['user']).first()

        # 不存在该用户 或 该用户不是店长 或 该用户不是老板
        if not user:
            return Response({
                'meta': meta_info('您为无效用户！请注册', 1)
            })

        try:
            owners = models.Clerks.objects.filter(shop__name=param['request_shop'])
        except Exception as e:
            print(repr(e))

            return Response({
                'meta': meta_info('获取店员信息出错！请重试', 2)
            })

        openids = []
        for owner in owners:
            openids.append(owner.openid)

        return Response({
            'message': {
                'openids': openids
            },
            'meta': meta_info('获取成功！', 0)
        })


class SendTransferCard(APIView):
    """发送调货请求卡片"""

    def get_owner_openid(self, shop: str):
        """获取请求调货目标分店店员的 openid"""

        try:
            clerks = models.Clerks.objects.filter(shop__name=shop)
            res = []
            for clerk in clerks:
                res.append(clerk.openid)

        except Exception as e:
            print(repr(e))

            return None

        return res

    @transaction.atomic
    def send_card(self, apply_shop: str, transfer_shop: str, openid: str, token: str, chicken: int, duck: int) -> int:
        """发送请求卡片"""

        # 获取调货分店鸡鸭库存
        rest_chicken, rest_duck = shop_rest(transfer_shop)

        # 如果请求量大于库存剩余量
        if chicken > rest_chicken or duck > rest_duck:
            return 6789

        # 拼接请求地址
        request_url = WXminiprogramInfo.send.format(token)

        # 创建保存点
        save_id = transaction.savepoint()

        try:
            # 创建调货记录
            record = models.Transfer.objects.create(
                apply_shop=models.Shops.objects.get(name=apply_shop),
                transfer_shop=models.Shops.objects.get(name=transfer_shop),
                chicken=chicken,
                duck=duck
            )

            # 构造模板显示内容字典
            data = {
                # 项目名称
                'thing6': {
                    'value': apply_shop + ' 请求调货！'
                },
                # 要货内容
                'thing3': {
                    'value': '鸡： ' + str(chicken) + ' 只  鸭： ' + str(duck) + ' 只'
                },
                # 备注
                'thing7': {
                    'value': '点击卡片处理调货请求'
                }
            }

            # 构造参数字典
            param = {
                'access_token': token,
                'touser': openid,
                'template_id': WXminiprogramInfo.template_ids['sendTransfer'],
                'page': 'pages/handle/transfer?request_shop=3&chicken=0&duck=5&id=' + str(record.id),
                'data': data
            }

            # 发起 post 请求
            response = requests.post(request_url, json.dumps(param))

            # 把返回结果转换成 json 字典
            response_json = response.json()

        except Exception as e:
            print(repr(e))

            # 创建记录失败，回滚到保存点处
            transaction.savepoint_rollback(save_id)
            return 12345

        # 保存点业务提交
        transaction.savepoint_commit(save_id)
        return response_json.get('errcode')

    def post(self, request):
        param = request.data
        user = models.Clerks.objects.filter(openid=param['user']).first()
        # 不存在该用户 或 该用户不是店长 或 该用户不是老板
        if not user:
            return Response({
                'meta': meta_info('您是无效用户！请注册', 1)
            })

        # 获取调货分店店长 openid
        openids = self.get_owner_openid(param['request_shop'])

        if openids is None:
            return Response({
                'meta': meta_info('调货分店店员信息错误！请重试', 2)
            })

        # openid 获取成功，获取 access_token 相关信息
        token_msg = WXminiprogramInfo.get_token()

        # token 获取失败
        if not token_msg.get('access_token'):
            return Response({
                'meta': meta_info('token 获取失败', token_msg.get('errcode'))
            })

        # 获取调货分店鸡鸭库存
        rest_chicken, rest_duck = shop_rest(param['shop'])

        # 如果请求量大于库存剩余量
        if param['chicken'] > rest_chicken or param['duck'] > rest_duck:
            return Response({
                'meta': meta_info('请求量大于库存剩余量，无法申请', 3)
            })

        # 未开启权限成员列表
        fail_message = []

        # token 获取成功，请求发送请求卡片
        for openid in openids:
            # 发送请求失败
            errcode = self.send_card(
                param['shop'],
                param['request_shop'],
                openid,
                token_msg.get('access_token'),
                param['chicken'],
                param['duck']
            )

            # 对方未打开订阅权限
            if errcode == 43101:
                fail_message.append(models.Clerks.objects.get(openid=openid).WXname)

            # 发送请求其他错误
            elif errcode != 0:
                return Response({
                    'meta': meta_info('调货请求失败！请重试', 4)
                })

        if len(fail_message) == len(openids):
            return Response({
                'meta': meta_info('对方所有店员拒绝接受消息，请联系他们在首页开启调货权限', 5)
            })
        # 发送请求成功
        return Response({
            'message': fail_message,
            'meta': meta_info('调货请求成功！等待对方同意', 0)
        })


class HandleTransfer(APIView):
    """处理调货请求"""

    # 结果字典
    res_dict = {
        1: '同意',
        2: '拒绝'
    }

    @transaction.atomic
    def change(self, apply_shop: models.Shops, transfer_shop: models.Shops, chicken: int, duck: int):
        """改变分店库存"""

        # 创建保存点
        save_id = transaction.savepoint()
        try:
            # 修改请求分店和调货分店的库存量
            apply_shop.chicken = F('chicken') + chicken
            apply_shop.duck = F('duck') + duck
            transfer_shop.chicken = F('chicken') - chicken
            transfer_shop.duck = F('duck') - duck
            apply_shop.save()
            transfer_shop.save()
        except Exception as e:
            print(repr(e))

            # 修改库存失败，回滚到保存点处
            transaction.savepoint_rollback(save_id)
            return False

        # 保存点业务提交
        transaction.savepoint_commit(save_id)
        return True

    def send_result_card(self, openid: str, token: str, res: int, apply_date: datetime.date, shop: models.Shops,
                         chicken: int, duck: int, handle_date: datetime.date):
        """发送处理结果卡片"""

        # 拼接请求地址
        request_url = WXminiprogramInfo.send.format(token)

        # 拼接模板内容
        data = {
            # 申请进度
            'thing1': {
                'value': self.res_dict[res] + '调货！'
            },
            'time6': {
                'value': str(apply_date)
            },
            # 申请项目
            'thing2': {
                'value': '向 ' + shop.name + ' 申请 鸡 ' + str(chicken) + ' 鸭 ' + str(duck)
            },
            # 操作时间
            'time4': {
                'value': str(handle_date)
            }
        }

        # 构造参数字典
        param = {
            'access_token': token,
            'touser': openid,
            'template_id': WXminiprogramInfo.template_ids['handleTransfer'],
            'page': 'pages/index/index',
            'data': data
        }

        # 发起 post 请求
        response = requests.post(request_url, json.dumps(param))

        # 把返回结果转换成 json 字典
        response_json = response.json()
 
        return response_json.get('errcode')

    @transaction.atomic
    def post(self, request):
        param = request.data

        user = models.Clerks.objects.filter(openid=param['user']).first()
        # 不存在该用户 或 该用户不是老板 并且 该用户是店员但不属于该分店
        if not user or (user.identity != 2 and user.shop.name != param['shop']):
            return Response({
                'meta': meta_info('操作权限出错！', 1)
            })

        try:
            transfer = models.Transfer.objects.get(id=param['id'])
        except Exception as e:
            print(repr(e))

            return Response({
                'meta': meta_info('调货记录出错！请重试', 2)
            })

        if transfer.handler is not None:
            return Response({
                 'meta': meta_info('该调货已被' + transfer.handler.WXname + '处理!', 3)
            })

        try:
            # 创建保存点
            save_id = transaction.savepoint()

            # 添加调货记录处理时间
            transfer.handle_date = datetime.date.today()
            # 修改调货记录状态
            transfer.status = int(param['status'])
            # 添加处理人
            transfer.handler = user
            # 保存调货记录实例
            transfer.save()

            # 同意调货改变 申请分店和调货分店的库存量
            if int(param['status']) == 1:
                res = self.change(transfer.apply_shop, transfer.transfer_shop, transfer.chicken, transfer.duck)
                # 修改途中出现错误
                if not res:
                    return Response({
                        'meta': meta_info('分店库存修改失败！请重试', 4)
                    })

            # 获取 token
            token_msg = WXminiprogramInfo.get_token()

            # token 获取失败
            if not token_msg.get('access_token'):
                return Response({
                    'meta': meta_info('token 获取失败', token_msg.get('errcode'))
                })

            # token获取成功，获取申请分店的店员实例
            owners = models.Clerks.objects.filter(shop=transfer.apply_shop)

            # 店长实例获取失败
            if not owners:
                return Response({
                    'meta': meta_info('申请调货分店店长查找失败！请重试', 5)
                })

            # 未开启权限成员列表
            fail_message = []

            # 店员实例获取成功，请求发送请求卡片
            for owner in owners:
                errcode = self.send_result_card(
                    owner.openid,
                    token_msg.get('access_token'),
                    transfer.status,
                    transfer.apply_date,
                    transfer.transfer_shop,
                    transfer.chicken, transfer.duck,
                    transfer.handle_date
                )

                if errcode != 0:
                    # 对方未打开订阅权限
                    if errcode == 43101:
                        fail_message.append(owner.WXname)
                    # 发送请求其他错误
                    else:
                        # 卡片发送失败，回滚到保存点处
                        transaction.savepoint_rollback(save_id)

                        return Response({
                            'meta': meta_info('卡片发送失败！请重试', 5)
                        })
        except Exception as e:
            print(repr(e))

            # 记录处理失败，回滚到保存点处
            transaction.savepoint_rollback(save_id)
            return Response({
                'meta': meta_info('处理出错！请重试', 6)
            })
        else:
            # 保存点业务提交
            transaction.savepoint_commit(save_id)

            # 调货分店店员全部为开启接收调货信息权限
            if len(fail_message) == len(owners):
                return Response({
                    'message': fail_message,
                    'meta': meta_info('对方所有店员拒绝接受消息，只能在“管理调货”中查看', 7)
                })
            else:
                return Response({
                    'message': fail_message,
                    'meta': meta_info('处理成功！', 0)
                })


class SearchTransferAPIView(APIView):
    """调货管理查询"""

    def post(self, request):
        param = request.data
        user = models.Clerks.objects.filter(openid=param['user']).first()
        # 不存在该用户 或 该用户不是老板也不是店长
        if not user:
            return Response({
                'meta': meta_info('您为无效用户！请注册', 1)
            })

        try:
            transfer_list = models.Transfer.objects.filter(transfer_shop__name=param['shop'], status=param['type'])
        except Exception as e:
            print(repr(e))

            return Response({
                'meta': meta_info('内容获取失败！请重试', 2)
            })

        # 按每页 10 条数据进行分页
        try:
            transfer_page = paging(transfer_list, 10, param['page'])
        except Exception as e:
            return Response({
                'meta': meta_info(str(e), 3)
            })

        # 初始化结果列表
        res_list = []

        for item in transfer_page:
            # 初始化中间字典
            temp = dict()
            temp['shop'] = item.apply_shop.name

            # 未处理的调货记录，携带记录 id
            if param['type'] == 0:
                temp['apply_date'] = item.apply_date
                temp['id'] = item.id
            # 已经处理的调货记录，携带“处理时间”
            else:
                temp['handle_date'] = item.handle_date
                temp['handler'] = item.handler.WXname

            temp['chicken'] = item.chicken
            temp['duck'] = item.duck
            res_list.append(temp)

        return Response({
            'message': {
                'res': res_list,
                'total': len(transfer_list)
            },
            'meta': meta_info('查询成功！', 0)
        })
